﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

using Microsoft.SharePoint;
using Microsoft.SharePoint.Client;
using System.IO;
using Microsoft.SharePoint.Client.Workflow;

namespace CompareWeb
{
    class Program
    {
        enum SharePointArtifactType
        {
            DocumentLibrary,
            List,
            Feature,
            Workflow
        }

        private static string[] compareItems = null;
        private static string[] CompareItems
        {
            get
            {
                if (compareItems == null)
                {
                    compareItems = ConfigurationManager.AppSettings["CompareItems"].Split(new char[] { ';' }, StringSplitOptions.RemoveEmptyEntries);
                }
                return compareItems;
            }
        }

        private static string url1 = null;
        private static string Url1
        {
            get
            {
                if (url1 == null)
                {
                    url1 = ConfigurationManager.AppSettings["Url1"];
                }
                return url1;
            }
        }

        private static string url2 = null;
        private static string Url2
        {
            get
            {
                if (url2 == null)
                {
                    url2 = ConfigurationManager.AppSettings["Url2"];
                }
                return url2;
            }
        }

        private static bool CompareHiddenList
        {
            get
            {
                return Convert.ToBoolean(ConfigurationManager.AppSettings["CompareHiddenList"]);
            }
        }

        private static bool CompareHiddenField
        {
            get
            {
                return Convert.ToBoolean(ConfigurationManager.AppSettings["CompareHiddenField"]);
            }
        }

        private static SharePointArtifactType[] artifactToCompare = null;
        private static SharePointArtifactType[] ArtifactToCompare
        {
            get
            {
                if (artifactToCompare == null)
                {
                    List<SharePointArtifactType> artifactTypes = new List<SharePointArtifactType>();
                    foreach (string artifact in ConfigurationManager.AppSettings["ArtifactToCompare"].Split(new char[] { ';' }, StringSplitOptions.RemoveEmptyEntries))
                    {
                        artifactTypes.Add((SharePointArtifactType)Enum.Parse(typeof(SharePointArtifactType), artifact));
                    }

                    artifactToCompare = artifactTypes.ToArray();
                }
                return artifactToCompare;
            }
        }

        private static List<string> listToCompare = null;
        private static List<string> ListToCompare
        {
            get
            {
                if (listToCompare == null)
                {
                    List<string> lists = new List<string>();
                    foreach (string listName in ConfigurationManager.AppSettings["ListToCompare"].Split(new char[] { ';' }, StringSplitOptions.RemoveEmptyEntries))
                    {
                        lists.Add(listName);
                    }

                    listToCompare = lists;
                }
                return listToCompare;
            }
        }

        private static List<string> documentLibraryToCompare = null;
        private static List<string> DocumentLibraryToCompare
        {
            get
            {
                if (documentLibraryToCompare == null)
                {
                    List<string> lists = new List<string>();
                    foreach (string listName in ConfigurationManager.AppSettings["DocumentLibraryToCompare"].Split(new char[] { ';' }, StringSplitOptions.RemoveEmptyEntries))
                    {
                        lists.Add(listName);
                    }

                    documentLibraryToCompare = lists;
                }
                return documentLibraryToCompare;
            }
        }

        static void Main(string[] args)
        {
            try
            {
                if (string.IsNullOrEmpty(ConfigurationManager.AppSettings["Url1"]))
                {
                    throw new Exception("Missing parameter: Url1");
                }

                if (string.IsNullOrEmpty(ConfigurationManager.AppSettings["Url2"]))
                {
                    throw new Exception("Missing parameter: Url2");
                }

                string logFolderPath = Path.Combine(Environment.CurrentDirectory, "Log");
                if (!Directory.Exists(logFolderPath))
                {
                    Directory.CreateDirectory(logFolderPath);
                }

                CompareWebsite(Program.Url1, Program.Url2, Program.ArtifactToCompare, logFolderPath);
            }
            catch (Exception ex)
            {
                Console.ForegroundColor = ConsoleColor.Red;

                Console.WriteLine("=================================================================");
                Console.WriteLine(ex.ToString());
                Console.WriteLine("=================================================================");
            }

            Console.ForegroundColor = ConsoleColor.White;
            Console.WriteLine();
            Console.WriteLine("Press any key to continue...");
            Console.ReadLine();
        }

        static void clientContext_ExecutingWebRequest(object sender, WebRequestEventArgs e)
        {
            try
            {
                e.WebRequestExecutor.WebRequest.Headers.Add("X-FORMS_BASED_AUTH_ACCEPTED", "f");
            }
            catch
            {
                throw;
            }
        }

        private static void CompareWebsite(string url1, string url2, SharePointArtifactType[] artifacts, string logFolderPath)
        {
            Console.ForegroundColor = ConsoleColor.Yellow;
            Console.WriteLine("=================================================================");
            Console.WriteLine(string.Format("Url1: {0}", url1));
            Console.WriteLine(string.Format("Url2: {0}", url2));

            foreach (SharePointArtifactType artifact in artifacts)
            {
                try
                {
                    Console.ForegroundColor = ConsoleColor.Yellow;
                    Console.WriteLine("=================================================================");
                    Console.WriteLine(string.Format("Comparing {0}...", artifact));
                    Console.WriteLine("=================================================================");

                    string fileName = string.Format("{0}_{1}.xml", artifact, DateTime.Now.ToString("yyyy-MM-dd_hhmmss"));
                    fileName = Path.Combine(logFolderPath, fileName);

                    WriteToFile(fileName, "<?xml version=\"1.0\" encoding=\"utf-8\" ?>");

                    WriteToFile(fileName, string.Format("<{0}s Url1=\"{1}\" Url2=\"{2}\">", artifact, url1, url2));
                    CompareWebsiteArtifact(url1, url2, artifact, fileName);
                    WriteToFile(fileName, string.Format("</{0}s>", artifact));
                }
                catch (Exception ex)
                {
                    Console.ForegroundColor = ConsoleColor.Red;

                    Console.WriteLine("=================================================================");
                    Console.WriteLine(string.Format("Error while comparing: {0}{1}Exception: {2}", artifact, Environment.NewLine, ex.ToString()));
                    Console.WriteLine("=================================================================");
                }
            }
        }

        private static void CompareWebsiteArtifact(string url1, string url2, SharePointArtifactType artifact, string fileName)
        {
            using (SPSite spSite = new SPSite(url1))
            {
                using (SPWeb spWeb = spSite.OpenWeb())
                {
                    foreach (SPList spList in spWeb.Lists)
                    {
                        if (Program.CompareHiddenList == false && spList.Hidden == true)
                        {
                            continue;
                        }

                        switch (artifact)
                        {
                            case SharePointArtifactType.DocumentLibrary:
                                if (spList is SPDocumentLibrary)
                                {
                                    if (Program.DocumentLibraryToCompare.Count > 0)
                                    {
                                        if (!Program.DocumentLibraryToCompare.Contains(spList.RootFolder.Url))
                                        {
                                            continue;
                                        }
                                    }

                                    Console.ForegroundColor = ConsoleColor.Green;
                                    Console.WriteLine(string.Format("Comparing {0}...", spList.RootFolder.Url));

                                    CompareDocumentLibrary(url2, spList, fileName);
                                }
                                break;
                            case SharePointArtifactType.List:
                                if (spList is SPDocumentLibrary)
                                {

                                }
                                else
                                {
                                    if (Program.ListToCompare.Count > 0)
                                    {
                                        if (!Program.ListToCompare.Contains(spList.RootFolder.Url))
                                        {
                                            continue;
                                        }
                                    }

                                    Console.ForegroundColor = ConsoleColor.Green;
                                    Console.WriteLine(string.Format("Comparing {0}...", spList.RootFolder.Url));

                                    CompareList(url2, spList, fileName);
                                }
                                break;
                            case SharePointArtifactType.Workflow:

                                Console.ForegroundColor = ConsoleColor.Green;
                                Console.WriteLine(string.Format("Comparing {0}...", spList.RootFolder.Url));

                                CompareWorkflow(url2, spList, fileName);
                                break;
                        }
                    }

                    switch (artifact)
                    {
                        case SharePointArtifactType.Feature:
                            CompareFeature(spWeb, url2, fileName);
                            break;
                    }
                }
            }
        }

        private static void CompareWorkflow(string url2, SPList spList, string fileName)
        {
            ClientContext clientContext = new ClientContext(url2);
            clientContext.ExecutingWebRequest += clientContext_ExecutingWebRequest;
            Web oWebsite = clientContext.Web;
            ListCollection collList = oWebsite.Lists;
            clientContext.Load(collList);
            clientContext.ExecuteQuery();

            List list = IsListExists(collList, spList.Title);

            if (list != null)
            {
                WorkflowAssociationCollection collWorkflow = list.WorkflowAssociations;
                clientContext.Load(collWorkflow);
                clientContext.ExecuteQuery();

                StringBuilder sb = new StringBuilder();

                foreach (SPWorkflowAssociation workflow in spList.WorkflowAssociations)
                {
                    WorkflowAssociation wa = IsWorkflowExists(collWorkflow, workflow.Name);
                    if (wa == null)
                    {
                        Console.ForegroundColor = ConsoleColor.Red;
                        Console.Write("MISSING WORKFLOW:");

                        Console.ForegroundColor = ConsoleColor.DarkYellow;
                        Console.WriteLine(string.Format("{0}", workflow.Name));

                        sb.AppendFormat("<Workflow Name=\"{0}\" Status=\"Missing\" />", workflow.Name);
                    }
                }

                if (sb.Length > 0)
                {
                    WriteToFile(fileName, string.Format("<List Title='{0}'>{1}</List>", spList.RootFolder, sb.ToString()));
                }
            }
        }

        private static WorkflowAssociation IsWorkflowExists(WorkflowAssociationCollection collWorkflow, string workflowName)
        {
            foreach (WorkflowAssociation wa in collWorkflow)
            {
                if (string.Compare(wa.Name, workflowName, true) == 0)
                {
                    return wa;
                }
            }

            return null;
        }

        private static void CompareFeature(SPWeb spWeb, string url2, string fileName)
        {
            ClientContext clientContext = new ClientContext(url2);
            clientContext.ExecutingWebRequest += clientContext_ExecutingWebRequest;
            FeatureCollection features = clientContext.Web.Features;
            clientContext.Load(features);
            clientContext.ExecuteQuery();

            foreach (SPFeature feature in spWeb.Features)
            {
                Feature searchResult = features.FirstOrDefault(f => f.DefinitionId == feature.DefinitionId);

                if (searchResult == null)
                {
                    Console.ForegroundColor = ConsoleColor.Red;
                    Console.Write("MISSING FEATURE:");

                    Console.ForegroundColor = ConsoleColor.DarkYellow;
                    Console.WriteLine(string.Format("{0}:{1}", feature.Definition.DisplayName, feature.DefinitionId));

                    WriteToFile(fileName, string.Format("<Feature><DisplayName>{0}</DisplayName><DefinitionId>{1}</DefinitionId></Feature>", feature.Definition.DisplayName, feature.DefinitionId));
                }
            }
        }

        private static List IsListExists(ListCollection collList, string listName)
        {
            foreach (List oList in collList)
            {
                if (string.Compare(oList.Title, listName, true) == 0)
                {
                    return oList;
                }
            }

            return null;
        }

        private static void CompareList(string url2, SPList spList, string fileName)
        {
            bool listFound = false;
            List foundList = null;

            ClientContext clientContext = new ClientContext(url2);
            clientContext.ExecutingWebRequest += clientContext_ExecutingWebRequest;
            Web oWebsite = clientContext.Web;
            ListCollection collList = oWebsite.Lists;
            clientContext.Load(collList);
            clientContext.ExecuteQuery();

            foreach (List oList in collList)
            {
                if (string.Compare(oList.Title, spList.Title, true) == 0)
                {
                    listFound = true;
                    foundList = oList;
                    break;
                }
            }

            if (!listFound)
            {
                Console.ForegroundColor = ConsoleColor.Red;
                Console.Write("MISSING LIST:");
                Console.ForegroundColor = ConsoleColor.DarkYellow;
                Console.WriteLine(spList.RootFolder);

                WriteToFile(fileName, string.Format("<List Title='{0}' Status='MissingList'></List>", spList.RootFolder));
            }
            else
            {
                string columnDifference = CompareColumns(clientContext, spList, foundList, fileName);

                string listItemsDifference = string.Empty;
                foreach (string listDetails in Program.CompareItems)
                {
                    string[] listDetail = listDetails.Split(new char[] { '.' });

                    if (String.Compare(listDetail[0], spList.RootFolder.Url, true) == 0)
                    {
                        listItemsDifference = CompareListItems(url2, spList, listDetail[1]);
                        break;
                    }
                }

                if (!string.IsNullOrEmpty(columnDifference) || !string.IsNullOrEmpty(listItemsDifference))
                {
                    WriteToFile(fileName, string.Format("<List Title='{0}'>{1}{2}</List>", spList.RootFolder, columnDifference, listItemsDifference));
                }
            }
        }

        private static string CompareListItems(string url2, SPList spList, string columnName)
        {
            List<string> fieldList = new List<string>();
            StringBuilder sbViewFields = new StringBuilder();

            foreach (SPContentType ct in spList.ContentTypes)
            {
                foreach (SPField field in ct.Fields)
                {
                    if (field.Hidden)
                    {
                        continue;
                    }

                    if (!fieldList.Contains(field.InternalName))
                    {
                        fieldList.Add(field.InternalName);
                    }
                }
            }

            if (!fieldList.Contains("FSObjType"))
            {
                sbViewFields.Append("<FieldRef Name='FSObjType' />");
            }
            if (!fieldList.Contains("FileDirRef"))
            {
                sbViewFields.Append("<FieldRef Name='FileDirRef' />");
            }
            if (!fieldList.Contains("FSObjType"))
            {
                sbViewFields.Append("<FieldRef Name='FSObjType' />");
            }

            foreach (string fieldInternalName in fieldList)
            {
                sbViewFields.AppendFormat("<FieldRef Name='{0}' />", fieldInternalName);
            }

            string itemDiff = CompareListItems(url2, spList, columnName, string.Empty, sbViewFields.ToString(), fieldList);

            if (itemDiff.Length > 0)
            {
                return string.Format("<Items>{0}</Items>", itemDiff);
            }
            else
            {
                return string.Empty;
            }
        }

        private static string CompareListItems(string url2, SPList spList, string columnName, string folder, string viewFields, List<string> fieldList)
        {
            StringBuilder content = new StringBuilder();

            SPQuery spQuery = new SPQuery();
            spQuery.ViewFields = viewFields;

            if (!string.IsNullOrEmpty(folder))
            {
                spQuery.Folder = spList.ParentWeb.GetFolder(folder);
            }

            foreach (SPListItem spListItem in spList.GetItems(spQuery))
            {

                int itemType = Convert.ToInt32(Convert.ToString(spListItem[SPBuiltInFieldId.FSObjType]));
                if (itemType == 1)
                {
                    string itemFolder = string.Format("{0}/{1}", Convert.ToString(spListItem[SPBuiltInFieldId.FileDirRef]), spListItem.Name);

                    Console.ForegroundColor = ConsoleColor.Green;
                    Console.WriteLine(itemFolder);

                    content.Append(CompareListItems(url2, spList, columnName, itemFolder, viewFields, fieldList));
                }
                else
                {
                    CamlQuery camlQuery = new CamlQuery();
                    camlQuery.ViewXml = string.Format("<View Scope='RecursiveAll'>" +
                                                        "<Query>" +
                                                            "<Where>" +
                                                                "<Eq>" +
                                                                    "<FieldRef Name='{0}' />" +
                                                                    "<Value Type='Text'>{1}</Value>" +
                                                                "</Eq>" +
                                                            "</Where>" +
                                                        "</Query>" +
                                                        "<ViewFields>" +
                                                            "<FieldRef Name='{0}' />" +
                                                        "</ViewFields>" +
                                                    "</View>", columnName, spListItem[columnName]);

                    ClientContext clientContext = new ClientContext(url2);
                    clientContext.ExecutingWebRequest += clientContext_ExecutingWebRequest;
                    List foundList = clientContext.Web.Lists.GetByTitle(spList.Title);
                    ListItemCollection collListItem = foundList.GetItems(camlQuery);
                    clientContext.Load(collListItem);
                    clientContext.ExecuteQuery();

                    if (collListItem.Count <= 0)
                    {
                        Console.ForegroundColor = ConsoleColor.Red;
                        Console.Write("MISSING ITEM:");

                        Console.ForegroundColor = ConsoleColor.DarkYellow;
                        Console.WriteLine(string.Format("{0}:{1}", spList.RootFolder, spListItem[columnName]));

                        content.AppendFormat("<Item Title='{0}' Status='Missing'><Values>{1}</Values></Item>", spListItem[columnName], GetMetadata(spListItem, fieldList));
                    }
                }
            }

            return content.ToString();
        }

        private static string GetMetadata(SPListItem spListItem, List<string> fieldList)
        {
            StringBuilder fieldValue = new StringBuilder();
            foreach (string internalName in fieldList)
            {
                fieldValue.AppendFormat("<Value InternalName=\"{0}\" Value=\"{1}\" />", internalName, SecurityElement.Escape(Convert.ToString(spListItem[internalName])));
                //fieldValue.AppendFormat("<Value InternalName=\"{0}\" Value=\"{1}\" />", internalName, Convert.ToString(spListItem[internalName]));
            }
            return fieldValue.ToString();
        }

        private static void CompareDocumentLibrary(string url2, SPList spList, string fileName)
        {
            bool listFound = false;
            List foundList = null;

            ClientContext clientContext = new ClientContext(url2);
            clientContext.ExecutingWebRequest += clientContext_ExecutingWebRequest;
            Web oWebsite = clientContext.Web;
            ListCollection collList = oWebsite.Lists;
            clientContext.Load(collList);
            clientContext.ExecuteQuery();

            foreach (List oList in collList)
            {
                if (string.Compare(oList.Title, spList.Title, true) == 0)
                {
                    listFound = true;
                    foundList = oList;
                    break;
                }
            }

            if (!listFound)
            {
                Console.ForegroundColor = ConsoleColor.Red;
                Console.Write("MISSING DOCUMENTLIBRARY:");
                Console.ForegroundColor = ConsoleColor.DarkYellow;
                Console.WriteLine(spList.RootFolder);

                WriteToFile(fileName, string.Format("<DocumentLibrary Title='{0}' Status='Missing'></DocumentLibrary>", spList.RootFolder));
            }
            else
            {
                string columnDifference = CompareColumns(clientContext, spList, foundList, fileName);

                string listItemsDifference = string.Empty;
                foreach (string listDetails in Program.CompareItems)
                {
                    string[] listDetail = listDetails.Split(new char[] { '.' });

                    if (String.Compare(listDetail[0], spList.RootFolder.Url, true) == 0)
                    {
                        listItemsDifference = CompareListItems(url2, spList, listDetail[1]);
                        break;
                    }
                }

                if (!string.IsNullOrEmpty(columnDifference) || !string.IsNullOrEmpty(listItemsDifference))
                {
                    WriteToFile(fileName, string.Format("<DocumentLibrary Title='{0}'>{1}{2}</DocumentLibrary>", spList.RootFolder, columnDifference, listItemsDifference));
                }
            }
        }

        private static string CompareColumns(ClientContext clientContext, SPList spList, List foundList, string fileName)
        {
            FieldCollection fields = foundList.Fields;
            clientContext.Load(fields);
            clientContext.ExecuteQuery();

            StringBuilder content = new StringBuilder();

            foreach (SPField spField in spList.Fields)
            {
                if (Program.CompareHiddenField == false && spField.Hidden == true)
                {
                    continue;
                }

                Field searchResult = fields.SingleOrDefault(f => f.InternalName == spField.InternalName);

                if (searchResult == null)
                {
                    Console.ForegroundColor = ConsoleColor.Red;
                    Console.Write("MISSING COLUMN:");

                    Console.ForegroundColor = ConsoleColor.DarkYellow;
                    Console.WriteLine(string.Format("{0}.{1}", spList.RootFolder, spField.InternalName));
                    Console.WriteLine(string.Format("SCHEMA:{0}", spField.SchemaXml));

                    content.AppendFormat("<Column Title='{0}' Status='MissingColumn'><SchemaXml>{1}</SchemaXml></Column>", spField.InternalName, spField.SchemaXml);
                }
                else
                {
                    if (string.Compare(searchResult.TypeAsString, spField.TypeAsString, true) != 0)
                    {
                        Console.ForegroundColor = ConsoleColor.Red;
                        Console.Write("MISSMATCH COLUMN TYPE:");

                        Console.ForegroundColor = ConsoleColor.DarkYellow;
                        Console.WriteLine(string.Format("{0}:{1}", spList.RootFolder, spField.InternalName, spField.SchemaXml));

                        Console.ForegroundColor = ConsoleColor.Red;
                        Console.Write("OLD SCHEMA:");

                        Console.ForegroundColor = ConsoleColor.DarkYellow;
                        Console.WriteLine(searchResult.SchemaXml);

                        Console.ForegroundColor = ConsoleColor.Red;
                        Console.Write("NEW SCHEMA:");

                        Console.ForegroundColor = ConsoleColor.DarkYellow;
                        Console.WriteLine(spField.SchemaXml);

                        content.AppendFormat("<Column Title='{0}' Status='MissmatchType'><OldSchema>{1}</OldSchema><NewSchema>{2}</NewSchema></Column>", spField.InternalName, searchResult.SchemaXml, spField.SchemaXml);
                    }
                }
            }

            if (content.Length > 0)
            {
                return string.Format("<Columns>{0}</Columns>", content);
            }
            else
            {
                return string.Empty;
            }
        }

        private static void WriteToFile(string fileName, string content)
        {
            using (System.IO.StreamWriter file = new System.IO.StreamWriter(fileName, true))
            {
                file.WriteLine(content);
            }
        }
    }
}